#!/bin/bash

check_5() {
  logit ""
  local id="5"
  local desc="容器运行时保护"
  checkHeader="$id - $desc"
  info "$checkHeader"
  startsectionjson "$id" "$desc"
}

check_running_containers() {
  # 如果容器为空，则没有正在运行的容器
  if [ -z "$containers" ]; then
    info "  * 没有正在运行的容器, 跳过第 5 节"
    return
  fi
  #使循环注意符成为符合POSIX标准的换行符
  set -f; IFS=$'
  '
}

check_5_1() {
  if [ -z "$containers" ]; then
    return
  fi

  local id="5.1"
  local desc="启用 AppArmor 配置文件"
  local remediation="如果 AppArmor 适用于你的 Linux 操作系统，应该可以，或者，可以使用 Docker 的默认 AppArmor 策略。"
  local remediationImpact="容器将具有在 AppArmor 配置组件中定义的安全控制。需要注意的是，如果 AppArmor 配置组件配置错误，这可能会导致在容器的操作中产生问题。"
  local check="$id - $desc"
  starttestjson "$id" "$desc"

  fail=0
  no_apparmor_containers=""
  for c in $containers; do
    policy=$(docker inspect --format 'AppArmorProfile={{ .AppArmorProfile }}' "$c")

    if [ "$policy" = "AppArmorProfile=" ] || [ "$policy" = "AppArmorProfile=[]" ] || [ "$policy" = "AppArmorProfile=<no value>" ] || [ "$policy" = "AppArmorProfile=unconfined" ]; then
      # 如果是第一个容器，则测试失败
      if [ $fail -eq 0 ]; then
        warn -s "$check"
        warn "     * 没有找到 AppArmorProfile : $c"
        no_apparmor_containers="$no_apparmor_containers $c"
        fail=1
        continue
      fi
      warn "     * 没有找到 AppArmorProfile : $c"
      no_apparmor_containers="$no_apparmor_containers $c"
    fi
  done
  #我们检查了所有容器，没有发现没有AppArmor
  if [ $fail -eq 0 ]; then
    pass -s "$check"
    logcheckresult "PASS"
    return
  fi
  logcheckresult "WARN" "没有AppArmor配置文件的容器" "$no_apparmor_containers"
}

check_5_2() {
  if [ -z "$containers" ]; then
    return
  fi

  local id="5.2"
  local desc="设置 SElinux 安全选项"
  local remediation="设置SELinux 状态。设置SELinux 策略。为Docker 创建容器或导入SELinux 策略模板。在SELinux 的情况下，以守护进程模式启动Docker。使用安全选项启动Docker。"
  local remediationImpact="SELinux 中定义的任何限制都会策略引导您的容器。 需要注意的是，您的 SELinux 策略配置错误，这可能会影响到容器容器的正确运行。"
  local check="$id - $desc"
  starttestjson "$id" "$desc"

  fail=0
  no_securityoptions_containers=""
  for c in $containers; do
    policy=$(docker inspect --format 'SecurityOpt={{ .HostConfig.SecurityOpt }}' "$c")

    if [ "$policy" = "SecurityOpt=" ] || [ "$policy" = "SecurityOpt=[]" ] || [ "$policy" = "SecurityOpt=<no value>" ]; then
      # 如果是第一个容器，则测试失败
      if [ $fail -eq 0 ]; then
        warn -s "$check"
        warn "     * 没有找到 SecurityOptions : $c"
        no_securityoptions_containers="$no_securityoptions_containers $c"
        fail=1
        continue
      fi
      warn "     * 没有找到 SecurityOptions : $c"
      no_securityoptions_containers="$no_securityoptions_containers $c"
    fi
  done
  #我们浏览了所有容器，没有发现没有SELinux
  if [ $fail -eq 0 ]; then
    pass -s "$check"
    logcheckresult "PASS"
    return
  fi
  logcheckresult "WARN" "没有 SecurityOptions 的容器" "$no_securityoptions_containers"
}

check_5_3() {
  if [ -z "$containers" ]; then
    return
  fi

  local id="5.3"
  local desc="确保在容器内限制linux 内核功能（自动）"
  local remediation="您可以删除所有当前配置的功能，然后仅恢复您专门使用的功能： docker run --cap-drop=all --cap-add={<Capability 1>,<Capability 2>} <Run arguments> <Container图像名称或 ID> <命令>"
  local remediationImpact="对容器内进程的限制基于有效的 Linux 功能。删除 NET_RAW 功能会阻止容器创建原始套接字，这在大多数情况下是一种很好的安全实践，但可能会影响某些网络实用程序。"
  local check="$id - $desc"
  starttestjson "$id" "$desc"

  fail=0
  caps_containers=""
  for c in $containers; do
    container_caps=$(docker inspect --format 'CapAdd={{ .HostConfig.CapAdd}}' "$c")
    caps=$(echo "$container_caps" | tr "[:lower:]" "[:upper:]" | \
      sed 's/CAPADD/CapAdd/' | \
      sed -r "s/AUDIT_WRITE|CHOWN|DAC_OVERRIDE|FOWNER|FSETID|KILL|MKNOD|NET_BIND_SERVICE|NET_RAW|SETFCAP|SETGID|SETPCAP|SETUID|SYS_CHROOT|\s//g")

    if [ "$caps" != 'CapAdd=' ] && [ "$caps" != 'CapAdd=[]' ] && [ "$caps" != 'CapAdd=<no value>' ] && [ "$caps" != 'CapAdd=<nil>' ]; then
      # 如果是第一个容器，则测试失败
      if [ $fail -eq 0 ]; then
        warn -s "$check"
        warn "     * 添加的功能: $caps to $c"
        caps_containers="$caps_containers $c"
        fail=1
        continue
      fi
      warn "     * 添加的功能: $caps to $c"
      caps_containers="$caps_containers $c"
    fi
  done
  # 我们浏览了所有容器，没有发现具有额外功能的容器
  if [ $fail -eq 0 ]; then
    pass -s "$check"
    logcheckresult "PASS"
    return
  fi
  logcheckresult "WARN" "为容器添加的功能" "$caps_containers"
}

check_5_4() {
  if [ -z "$containers" ]; then
    return
  fi

  local id="5.4"
  local desc="确保不使用特权容器"
  local remediation="您不应该使用 --privileged 标志运行容器。"
  local remediationImpact="如果你在没有 --privileged 标志的情况下启动一个容器，它不会有过多的默认功能。"
  local check="$id - $desc"
  starttestjson "$id" "$desc"

  fail=0
  privileged_containers=""
  for c in $containers; do
    privileged=$(docker inspect --format '{{ .HostConfig.Privileged }}' "$c")

    if [ "$privileged" = "true" ]; then
      # 如果是第一个容器，则测试失败
      if [ $fail -eq 0 ]; then
        warn -s "$check"
        warn "     * 以特权模式运行的容器: $c"
        privileged_containers="$privileged_containers $c"
        fail=1
        continue
      fi
      warn "     * 以特权模式运行的容器: $c"
      privileged_containers="$privileged_containers $c"
    fi
  done
  # 我们遍历了所有容器，没有发现特权容器
  if [ $fail -eq 0 ]; then
    pass -s "$check"
    logcheckresult "PASS"
    return
  fi
  logcheckresult "WARN" "以特权模式运行的容器" "$privileged_containers"
}

check_5_5() {
  if [ -z "$containers" ]; then
    return
  fi

  local id="5.5"
  local desc="确保敏感的主机系统目录没有挂载容器（自动）"
  local remediation="您不应该在容器内的主机上挂载对安全敏感的目录，尤其是在读写模式下。"
  local remediationImpact="无"
  local check="$id - $desc"
  starttestjson "$id" "$desc"

  # 要测试的敏感目录列表。脚本使用换行符作为分隔符。
  # 注意缺少标识。它需要它进行子串比较。
  sensitive_dirs='/
/boot
/dev
/etc
/lib
/proc
/sys
/usr'
  fail=0
  sensitive_mount_containers=""
  for c in $containers; do
    volumes=$(docker inspect --format '{{ .Mounts }}' "$c")
    if docker inspect --format '{{ .VolumesRW }}' "$c" 2>/dev/null 1>&2; then
      volumes=$(docker inspect --format '{{ .VolumesRW }}' "$c")
    fi
    # 遍历敏感目录中的每个目录，看看它们是否存在于卷中
    for v in $sensitive_dirs; do
      sensitive=0
      if echo "$volumes" | grep -e "{.*\s$v\s.*true\s.*}" 2>/tmp/null 1>&2; then
        sensitive=1
      fi
      if [ $sensitive -eq 1 ]; then
        # 如果是第一个容器，则测试失败
        if [ $fail -eq 0 ]; then
          warn -s "$check"
          warn "     * 敏感目录 $v 挂载: $c"
          sensitive_mount_containers="$sensitive_mount_containers $c:$v"
          fail=1
          continue
        fi
        warn "     * 敏感目录 $v 挂载: $c"
        sensitive_mount_containers="$sensitive_mount_containers $c:$v"
      fi
    done
  done
  # 我们检查了所有的容器，没有发现任何带有敏感安装的容器
  if [ $fail -eq 0 ]; then
    pass -s "$check"
    logcheckresult "PASS"
    return
  fi
  logcheckresult "WARN" "安装了敏感目录的容器" "$sensitive_mount_containers"
}

check_5_6() {
  if [ -z "$containers" ]; then
    return
  fi

  local id="5.6"
  local desc="确保sshd不在容器中运行"
  local remediation="从容器中卸载 SSH 守护进程，并使用 docker exec 进入远程主机上的容器。"
  local remediationImpact="无"
  local check="$id - $desc"
  starttestjson "$id" "$desc"

  fail=0
  ssh_exec_containers=""
  printcheck=0
  for c in $containers; do

    processes=$(docker exec "$c" ps -el 2>/dev/null | grep -c sshd | awk '{print $1}')
    if [ "$processes" -ge 1 ]; then
      # 如果是第一个容器，则测试失败
      if [ $fail -eq 0 ]; then
        warn -s "$check"
        warn "     * 运行 sshd 的容器: $c"
        ssh_exec_containers="$ssh_exec_containers $c"
        fail=1
        printcheck=1
      else
        warn "     * 运行 sshd 的容器: $c"
        ssh_exec_containers="$ssh_exec_containers $c"
      fi
    fi

    exec_check=$(docker exec "$c" ps -el 2>/dev/null)
    if [ $? -eq 255 ]; then
        if [ $printcheck -eq 0 ]; then
          warn -s "$check"
          printcheck=1
        fi
      warn "     * Docker 执行失败: $c"
      ssh_exec_containers="$ssh_exec_containers $c"
      fail=1
    fi

  done
  # 我们浏览了所有容器，但没有发现 sshd
  if [ $fail -eq 0 ]; then
    pass -s "$check"
    logcheckresult "PASS"
    return
  fi
  logcheckresult "WARN" "sshd/docker exec 失败的容器" "$ssh_exec_containers"
}

check_5_7() {
  if [ -z "$containers" ]; then
    return
  fi

  local id="5.7"
  local desc="确保特权端口不被映射到容器内"
  local remediation="启动容器时，不应将容器端口映射到特权主机端口。您还应该确保没有这样的容器来托管 Dockerfile 中的特权端口映射声明。"
  local remediationImpact="无"
  local check="$id - $desc"
  starttestjson "$id" "$desc"

  fail=0
  privileged_port_containers=""
  for c in $containers; do
    # 端口格式为私有端口 -> ip: 公共端口
    ports=$(docker port "$c" | awk '{print $0}' | cut -d ':' -f2)

    # 遍历端口范围（行分隔）
    for port in $ports; do
      if [ -n "$port" ] && [ "$port" -lt 1024 ]; then
        # 如果是第一个容器，则测试失败
        if [ $fail -eq 0 ]; then
          warn -s "$check"
          warn "     * 正在使用的特权端口: $port in $c"
          privileged_port_containers="$privileged_port_containers $c:$port"
          fail=1
          continue
        fi
        warn "     * 正在使用的特权端口: $port in $c"
        privileged_port_containers="$privileged_port_containers $c:$port"
      fi
    done
  done
  # 我们遍历了所有容器，没有发现特权端口
  if [ $fail -eq 0 ]; then
    pass -s "$check"
    logcheckresult "PASS"
    return
  fi
  logcheckresult "WARN" "使用特权端口的容器" "$privileged_port_containers"
}

check_5_8() {
  if [ -z "$containers" ]; then
    return
  fi

  local id="5.8"
  local desc="确保容器只打开必要的端口"
  local remediation="您应该确保每个容器映像的 Dockerfile 仅公开所需的端口。"
  local remediationImpact="无"
  local check="$id - $desc"
  starttestjson "$id" "$desc"

  note -c "$check"
  logcheckresult "NOTE"
}

check_5_9() {
  if [ -z "$containers" ]; then
    return
  fi

  local id="5.9"
  local desc="确保主机的网络命名空间不被共享（自动）"
  local remediation="启动任何容器时不应传递 --net=host 选项。"
  local remediationImpact="无"
  local check="$id - $desc"
  starttestjson "$id" "$desc"

  fail=0
  net_host_containers=""
  for c in $containers; do
    mode=$(docker inspect --format 'NetworkMode={{ .HostConfig.NetworkMode }}' "$c")

    if [ "$mode" = "NetworkMode=host" ]; then
      # 如果是第一个容器，则测试失败
      if [ $fail -eq 0 ]; then
        warn -s "$check"
        warn "     * 以网络模式“主机”运行的容器: $c"
        net_host_containers="$net_host_containers $c"
        fail=1
        continue
      fi
      warn "     * 以网络模式“主机”运行的容器: $c"
      net_host_containers="$net_host_containers $c"
    fi
  done
  # 我们遍历了所有容器，没有发现网络模式主机
  if [ $fail -eq 0 ]; then
    pass -s "$check"
    logcheckresult "PASS"
    return
  fi
  logcheckresult "WARN" "以网络模式“主机”运行的容器" "$net_host_containers"
}

check_5_10() {
  if [ -z "$containers" ]; then
    return
  fi

  local id="5.10"
  local desc="确保容器的内存使用是有限制的"
  local remediation="您应该使用 --memory 参数仅使用所需的内存来运行容器。"
  local remediationImpact="如果没有为每个容器设置正确的内存限制，一个进程可能会扩大其使用范围并导致其他容器耗尽资源。"
  local check="$id - $desc"
  starttestjson "$id" "$desc"

  fail=0
  mem_unlimited_containers=""
  for c in $containers; do
    memory=$(docker inspect --format '{{ .HostConfig.Memory }}' "$c")
    if docker inspect --format '{{ .Config.Memory }}' "$c" 2> /dev/null 1>&2; then
      memory=$(docker inspect --format '{{ .Config.Memory }}' "$c")
    fi

    if [ "$memory" = "0" ]; then
      # 如果是第一个容器，则测试失败
      if [ $fail -eq 0 ]; then
        warn -s "$check"
        warn "      * 容器运行不受内存限制: $c"
        mem_unlimited_containers="$mem_unlimited_containers $c"
        fail=1
        continue
      fi
      warn "      * 容器运行不受内存限制: $c"
      mem_unlimited_containers="$mem_unlimited_containers $c"
    fi
  done
  # 我们遍历了所有的容器，发现不乏内存限制
  if [ $fail -eq 0 ]; then
    pass -s "$check"
    logcheckresult "PASS"
    return
  fi
  logcheckresult "WARN" "容器运行不受内存限制" "$mem_unlimited_containers"
}

check_5_11() {
  if [ -z "$containers" ]; then
    return
  fi

  local id="5.11"
  local desc="确保在容器上正确设置 CPU 优先级"
  local remediation="您应该根据容器在组织内的优先级来管理容器之间的 CPU 运行时。为此，使用 --cpu-shares 参数启动容器。"
  local remediationImpact="如果您没有正确分配 CPU 阈值，容器进程可能会耗尽资源并变得无响应。如果主机上的 CPU 资源不受限制，则 CPU 份额不会对单个资源设置任何限制。"
  local check="$id - $desc"
  starttestjson "$id" "$desc"

  fail=0
  cpu_unlimited_containers=""
  for c in $containers; do
    shares=$(docker inspect --format '{{ .HostConfig.CpuShares }}' "$c")
    if docker inspect --format '{{ .Config.CpuShares }}' "$c" 2> /dev/null 1>&2; then
      shares=$(docker inspect --format '{{ .Config.CpuShares }}' "$c")
    fi

    if [ "$shares" = "0" ]; then
      # 如果是第一个容器，则测试失败
      if [ $fail -eq 0 ]; then
        warn -s "$check"
        warn "      * 容器运行不受 CPU 限制: $c"
        cpu_unlimited_containers="$cpu_unlimited_containers $c"
        fail=1
        continue
      fi
      warn "      * 容器运行不受 CPU 限制: $c"
      cpu_unlimited_containers="$cpu_unlimited_containers $c"
    fi
  done
  # 我们遍历了所有容器，发现不乏CPUShare限制
  if [ $fail -eq 0 ]; then
    pass -s "$check"
    logcheckresult "PASS"
    return
  fi
  logcheckresult "WARN" "不受 CPU 限制运行的容器" "$cpu_unlimited_containers"
}

check_5_12() {
  if [ -z "$containers" ]; then
    return
  fi

  local id="5.12"
  local desc="确保容器的根文件系统以只读方式挂载"
  local remediation="您应该在容器的运行时添加 --read-only 标志以强制将容器的根文件系统挂载为只读。"
  local remediationImpact="如果未定义数据写入策略，则在容器运行时启用 --read-only 可能会破坏某些容器操作系统包。您应该定义容器的数据在运行时应该和不应该持久化的内容，以便决定使用哪种策略。"
  local check="$id - $desc"
  starttestjson "$id" "$desc"

  fail=0
  fsroot_mount_containers=""
  for c in $containers; do
   read_status=$(docker inspect --format '{{ .HostConfig.ReadonlyRootfs }}' "$c")

    if [ "$read_status" = "false" ]; then
      # 如果是第一个容器，则测试失败
      if [ $fail -eq 0 ]; then
        warn -s "$check"
        warn "      * 容器以 root FS 挂载 R/W 运行: $c"
        fsroot_mount_containers="$fsroot_mount_containers $c"
        fail=1
        continue
      fi
      warn "      * 容器以 root FS 挂载 R/W 运行: $c"
      fsroot_mount_containers="$fsroot_mount_containers $c"
    fi
  done
  # 我们遍历了所有容器，没有发现 R/W FS 挂载
  if [ $fail -eq 0 ]; then
    pass -s "$check"
    logcheckresult "PASS"
    return
  fi
  logcheckresult "WARN" "以 root FS 挂载 R/W 运行的容器" "$fsroot_mount_containers"
}

check_5_13() {
  if [ -z "$containers" ]; then
    return
  fi

  local id="5.13"
  local desc="确保进入容器的流量绑定到特定的主机接口"
  local remediation="您应该将容器端口绑定到所需主机端口上的特定主机接口。示例： docker run --detach --publish 10.2.3.4:49153:80 nginx 在此示例中，容器端口 80 绑定到主机端口 49153 上，并且仅接受来自 10.2.3.4 外部接口的传入连接。"
  local remediationImpact="无"
  local check="$id - $desc"
  starttestjson "$id" "$desc"

  fail=0
  incoming_unbound_containers=""
  for c in $containers; do
    for ip in $(docker port "$c" | awk '{print $3}' | cut -d ':' -f1); do
      if [ "$ip" = "0.0.0.0" ]; then
        # 如果是第一个容器，则测试失败
        if [ $fail -eq 0 ]; then
          warn -s "$check"
          warn "      * 端口绑定通配符IP: $ip in $c"
          incoming_unbound_containers="$incoming_unbound_containers $c:$ip"
          fail=1
          continue
        fi
        warn "      * 端口绑定通配符IP: $ip in $c"
        incoming_unbound_containers="$incoming_unbound_containers $c:$ip"
      fi
    done
  done
  # 我们遍历了所有的容器，发现没有绑定到 0.0.0.0 的端口
  if [ $fail -eq 0 ]; then
    pass -s "$check"
    logcheckresult "PASS"
    return
  fi
  logcheckresult "WARN" "端口绑定到通配符 IP 的容器" "$incoming_unbound_containers"
}

check_5_14() {
  if [ -z "$containers" ]; then
    return
  fi

  local id="5.14"
  local desc="确保“on-failur”容器重启策略设置为 5"
  local remediation="如果您希望容器自动重启，示例命令是 docker run --detach --restart=on-failure:5 nginx"
  local remediationImpact="如果设置了此选项，容器将仅尝试重新启动 5 次。"
  local check="$id - $desc"
  starttestjson "$id" "$desc"

  fail=0
  maxretry_unset_containers=""
  for c in $containers; do
    policy=$(docker inspect --format MaximumRetryCount='{{ .HostConfig.RestartPolicy.MaximumRetryCount }}' "$c")

    if [ "$policy" != "MaximumRetryCount=5" ]; then
      # 如果是第一个容器，则测试失败
      if [ $fail -eq 0 ]; then
        warn -s "$check"
        warn "      * MaximumRetryCount 未设置为 5: $c"
        maxretry_unset_containers="$maxretry_unset_containers $c"
        fail=1
        continue
      fi
      warn "      * MaximumRetryCount 未设置为 5: $c"
      maxretry_unset_containers="$maxretry_unset_containers $c"
    fi
  done
  # 我们浏览了所有的容器，它们都有 MaximumRetryCount=5
  if [ $fail -eq 0 ]; then
    pass -s "$check"
    logcheckresult "PASS"
    return
  fi
  logcheckresult "WARN" "MaximumRetryCount 未设置为 5 的容器" "$maxretry_unset_containers"
}

check_5_15() {
  if [ -z "$containers" ]; then
    return
  fi

  local id="5.15"
  local desc="确保主机的进程命名空间不共享"
  local remediation="您不应使用 --pid=host 参数启动容器。"
  local remediationImpact="容器进程无法看到主机系统上的进程。"
  local check="$id - $desc"
  starttestjson "$id" "$desc"

  fail=0
  pidns_shared_containers=""
  for c in $containers; do
    mode=$(docker inspect --format 'PidMode={{.HostConfig.PidMode }}' "$c")

    if [ "$mode" = "PidMode=host" ]; then
      # 如果是第一个容器，则测试失败
      if [ $fail -eq 0 ]; then
        warn -s "$check"
        warn "      * 共享的主机 PID 命名空间: $c"
        pidns_shared_containers="$pidns_shared_containers $c"
        fail=1
        continue
      fi
      warn "      * 共享的主机 PID 命名空间: $c"
      pidns_shared_containers="$pidns_shared_containers $c"
    fi
  done
  # 我们浏览了所有容器，没有发现以 PidMode 作为主机的容器
  if [ $fail -eq 0 ]; then
    pass -s "$check"
    logcheckresult "PASS"
    return
  fi
  logcheckresult "WARN" "共享主机PID命名空间的容器" "$pidns_shared_containers"
}

check_5_16() {
  if [ -z "$containers" ]; then
    return
  fi

  local id="5.16"
  local desc="确保主机的 IPC 命令空间不被共享"
  local remediation="您不应使用 --ipc=host 参数启动容器。"
  local remediationImpact="共享内存段用于加速进程间通信，通常在高性能应用程序中。如果这种类型的应用程序是"
  local check="$id - $desc"
  starttestjson "$id" "$desc"

  fail=0
  ipcns_shared_containers=""
  for c in $containers; do
    mode=$(docker inspect --format 'IpcMode={{.HostConfig.IpcMode }}' "$c")

    if [ "$mode" = "IpcMode=host" ]; then
      # 如果是第一个容器，则测试失败
      if [ $fail -eq 0 ]; then
        warn -s "$check"
        warn "      * 与主机共享的 IPC 命名空间: $c"
        ipcns_shared_containers="$ipcns_shared_containers $c"
        fail=1
        continue
      fi
      warn "      * 与主机共享的 IPC 命名空间: $c"
      ipcns_shared_containers="$ipcns_shared_containers $c"
    fi
  done
  # 我们浏览了所有容器，没有发现以 IPCMode 作为主机的容器
  if [ $fail -eq 0 ]; then
    pass -s "$check"
    logcheckresult "PASS"
    return
  fi
  logcheckresult "WARN" "容器共享主机 IPC 命名空间" "$ipcns_shared_containers"
}

check_5_17() {
  if [ -z "$containers" ]; then
    return
  fi

  local id="5.17"
  local desc="确保主机设备不直接暴露给容器"
  local remediation="您不应直接将主机设备暴露给容器。如果您确实需要向容器公开主机设备，则应根据您的组织使用细化权限。"
  local remediationImpact="容器化为多个容器时，您可能需要共享容器的 IPC 命名空间以实现高性能。在这些情况下，您仍然应该只共享容器特定的 IPC 命名空间，而不是主机 IPC 命名空间。"
  local check="$id - $desc"
  starttestjson "$id" "$desc"

  fail=0
  hostdev_exposed_containers=""
  for c in $containers; do
    devices=$(docker inspect --format 'Devices={{ .HostConfig.Devices }}' "$c")

    if [ "$devices" != "Devices=" ] && [ "$devices" != "Devices=[]" ] && [ "$devices" != "Devices=<no value>" ]; then
      # 如果是第一个容器，则测试失败
      if [ $fail -eq 0 ]; then
        info -c "$check"
        info "      * Container has devices exposed directly: $c"
        hostdev_exposed_containers="$hostdev_exposed_containers $c"
        fail=1
        continue
      fi
      info "      * Container has devices exposed directly: $c"
      hostdev_exposed_containers="$hostdev_exposed_containers $c"
    fi
  done
  # 我们检查了所有的容器，没有发现有设备
  if [ $fail -eq 0 ]; then
    pass -c "$check"
    logcheckresult "PASS"
    return
  fi
  logcheckresult "INFO" "Containers with host devices exposed directly" "$hostdev_exposed_containers"
}

check_5_18() {
  if [ -z "$containers" ]; then
    return
  fi

  local id="5.18"
  local desc="设置默认的 ulimit 配置（在需要时）"
  local remediation="如果在特定情况下需要，您应该只覆盖默认的 ulimit 设置。"
  local remediationImpact="您将无法直接在容器内使用主机设备。"
  local check="$id - $desc"
  starttestjson "$id" "$desc"

  fail=0
  no_ulimit_containers=""
  for c in $containers; do
    ulimits=$(docker inspect --format 'Ulimits={{ .HostConfig.Ulimits }}' "$c")

    if [ "$ulimits" = "Ulimits=" ] || [ "$ulimits" = "Ulimits=[]" ] || [ "$ulimits" = "Ulimits=<no value>" ]; then
      # 如果是第一个容器，则测试失败
      if [ $fail -eq 0 ]; then
        info -c "$check"
        info "      * Container no default ulimit override: $c"
        no_ulimit_containers="$no_ulimit_containers $c"
        fail=1
        continue
      fi
      info "      * Container no default ulimit override: $c"
      no_ulimit_containers="$no_ulimit_containers $c"
    fi
  done
  # 我们浏览了所有容器，没有发现没有 Ulimits
  if [ $fail -eq 0 ]; then
    pass -c "$check"
    logcheckresult "PASS"
    return
  fi
  logcheckresult "INFO" "Containers with no default ulimit override" "$no_ulimit_containers"
}

check_5_19() {
  if [ -z "$containers" ]; then
    return
  fi

  local id="5.19"
  local desc="确保挂载传播模式未设置为共享"
  local remediation="不要在共享模式传播中挂载卷。"
  local remediationImpact="如果 ulimit 设置不正确，单个容器的过度使用可能会使主机系统无法使用。"
  local check="$id - $desc"
  starttestjson "$id" "$desc"

  fail=0
  mountprop_shared_containers=""
  for c in $containers; do
    if docker inspect --format 'Propagation={{range $mnt := .Mounts}} {{json $mnt.Propagation}} {{end}}' "$c" | \
     grep shared 2>/dev/null 1>&2; then
      # 如果是第一个容器，则测试失败
      if [ $fail -eq 0 ]; then
        warn -s "$check"
        warn "      * 挂载传播方式共享: $c"
        mountprop_shared_containers="$mountprop_shared_containers $c"
        fail=1
        continue
      fi
      warn "      * 挂载传播方式共享: $c"
      mountprop_shared_containers="$mountprop_shared_containers $c"
    fi
  done
  # 我们遍历了所有容器，没有发现共享传播模式
  if [ $fail -eq 0 ]; then
    pass -s "$check"
    logcheckresult "PASS"
    return
  fi
  logcheckresult "WARN" "具有共享挂载传播的容器" "$mountprop_shared_containers"
}

check_5_20() {
  if [ -z "$containers" ]; then
    return
  fi

  local id="5.20"
  local desc="确保主机的 UTS 命令空间不被共享"
  local remediation="您不应使用 --uts=host 参数启动容器。"
  local remediationImpact="无"
  local check="$id - $desc"
  starttestjson "$id" "$desc"

  fail=0
  utcns_shared_containers=""
  for c in $containers; do
    mode=$(docker inspect --format 'UTSMode={{.HostConfig.UTSMode }}' "$c")

    if [ "$mode" = "UTSMode=host" ]; then
      # 如果是第一个容器，则测试失败
      if [ $fail -eq 0 ]; then
        warn -s "$check"
        warn "      * 与主机共享的 UTS 命名空间: $c"
        utcns_shared_containers="$utcns_shared_containers $c"
        fail=1
        continue
      fi
      warn "      * 与主机共享的 UTS 命名空间: $c"
      utcns_shared_containers="$utcns_shared_containers $c"
    fi
  done
  # 我们浏览了所有容器，没有发现以 UTSMode 作为主机的容器
  if [ $fail -eq 0 ]; then
    pass -s "$check"
    logcheckresult "PASS"
    return
  fi
  logcheckresult "WARN" "容器共享主机 UTS 命名空间" "$utcns_shared_containers"
}

check_5_21() {
  if [ -z "$containers" ]; then
    return
  fi

  local id="5.21"
  local desc="确保默认的 seccomp 配置文件未禁用"
  local remediation="默认情况下，seccomp 配置文件已启用。除非您想修改和使用修改后的 seccomp 配置文件，否则您无需执行任何操作。"
  local remediationImpact="无"
  local check="$id - $desc"
  starttestjson "$id" "$desc"

  fail=0
  seccomp_disabled_containers=""
  for c in $containers; do
    if docker inspect --format 'SecurityOpt={{.HostConfig.SecurityOpt }}' "$c" | \
      grep -E 'seccomp:unconfined|seccomp=unconfined' 2>/dev/null 1>&2; then
      # 如果是第一个容器，则测试失败
      if [ $fail -eq 0 ]; then
        warn -s "$check"
        warn "      * 默认 seccomp 配置文件已禁用: $c"
        seccomp_disabled_containers="$seccomp_disabled_containers $c"
        fail=1
      else
        warn "      * 默认 seccomp 配置文件已禁用: $c"
        seccomp_disabled_containers="$seccomp_disabled_containers $c"
      fi
    fi
  done
  # 我们浏览了所有容器，但没有发现禁用默认 secomp 配置文件的容器
  if [ $fail -eq 0 ]; then
    pass -s "$check"
    logcheckresult "PASS"
    return
  fi
  logcheckresult "WARN" "禁用默认 seccomp 配置文件的容器" "$seccomp_disabled_containers"
}

check_5_22() {
  if [ -z "$containers" ]; then
    return
  fi

  local id="5.22"
  local desc="确保docker exec 命令不能使用特权选项"
  local remediation="您不应在 docker exec 命令中使用 --privileged 选项。"
  local remediationImpact="对于 Docker 1.10 及更高版本，默认的 seccomp 配置文件会阻止系统调用，无论 --cap-add 传递给容器如何。"
  local check="$id - $desc"
  starttestjson "$id" "$desc"

  note -c "$check"
  logcheckresult "NOTE"
}

check_5_23() {
  if [ -z "$containers" ]; then
    return
  fi

  local id="5.23"
  local desc="确保docker exec 命令不使用 user =root选项"
  local remediation="您不应在 docker exec 命令中使用 --user=root 选项。"
  local remediationImpact="如果您需要容器内的增强功能，请使用它所需的所有权限运行它。这些应单独指定。"
  local check="$id - $desc"
  starttestjson "$id" "$desc"

  note -c "$check"
  logcheckresult "NOTE"
}

check_5_24() {
  if [ -z "$containers" ]; then
    return
  fi

  local id="5.24"
  local desc="确保 cgroug 的使用得到确认"
  local remediation="除非严格要求，否则不应在 docker run 命令中使用 --cgroup-parent 选项。"
  local remediationImpact="无"
  local check="$id - $desc"
  starttestjson "$id" "$desc"

  fail=0
  unexpected_cgroup_containers=""
  for c in $containers; do
    mode=$(docker inspect --format 'CgroupParent={{.HostConfig.CgroupParent }}x' "$c")

    if [ "$mode" != "CgroupParent=x" ]; then
      # 如果是第一个容器，则测试失败
      if [ $fail -eq 0 ]; then
        warn -s "$check"
        warn "      * 确认 cgroup 使用: $c"
        unexpected_cgroup_containers="$unexpected_cgroup_containers $c"
        fail=1
        continue
      fi
      warn "      * 确认 cgroup 使用: $c"
      unexpected_cgroup_containers="$unexpected_cgroup_containers $c"
    fi
  done
  # 我们浏览了所有容器，没有发现以 UTSMode 作为主机的容器
  if [ $fail -eq 0 ]; then
    pass -s "$check"
    logcheckresult "PASS"
    return
  fi
    logcheckresult "WARN" "使用意外 cgroup 的容器" "$unexpected_cgroup_containers"
}

check_5_25() {
  if [ -z "$containers" ]; then
    return
  fi
  local id="5.25"
  local desc="确保限制容器获得额外的权限"
  local remediation="您应该使用以下选项启动容器： docker run --rm -it --security-opt=no-new-privileges ubuntu bash"
  local remediationImpact="无"
  local check="$id - $desc"
  starttestjson "$id" "$desc"

  fail=0
  addprivs_containers=""
  for c in $containers; do
    if ! docker inspect --format 'SecurityOpt={{.HostConfig.SecurityOpt }}' "$c" | grep 'no-new-privileges' 2>/dev/null 1>&2; then
      # 如果是第一个容器，则测试失败
      if [ $fail -eq 0 ]; then
        warn -s "$check"
        warn "      * 特权不受限制: $c"
        addprivs_containers="$addprivs_containers $c"
        fail=1
        continue
      fi
      warn "      * 特权不受限制: $c"
      addprivs_containers="$addprivs_containers $c"
    fi
  done
  # 我们检查了所有容器，发现没有一个能够获得额外的权限
  if [ $fail -eq 0 ]; then
    pass -s "$check"
    logcheckresult "PASS"
    return
  fi
  logcheckresult "WARN" "没有限制权限的容器" "$addprivs_containers"
}

check_5_26() {
  if [ -z "$containers" ]; then
    return
  fi

  local id="5.26"
  local desc="确保检查容器运行时健康"
  local remediation="您应该使用 --health-cmd 参数运行容器。"
  local remediationImpact="no_new_priv 选项阻止像 SELinux 这样的 LSM 允许进程获取新权限。"
  local check="$id - $desc"
  starttestjson "$id" "$desc"

  fail=0
  nohealthcheck_containers=""
  for c in $containers; do
    if ! docker inspect --format '{{ .Id }}: Health={{ .State.Health.Status }}' "$c" 2>/dev/null 1>&2; then
      if [ $fail -eq 0 ]; then
        warn -s "$check"
        warn "      * 未设置健康检查: $c"
        nohealthcheck_containers="$nohealthcheck_containers $c"
        fail=1
        continue
      fi
      warn "      * 未设置健康检查: $c"
      nohealthcheck_containers="$nohealthcheck_containers $c"
    fi
  done
  if [ $fail -eq 0 ]; then
    pass -s "$check"
    logcheckresult "PASS"
    return
  fi
  logcheckresult "WARN" "没有健康检查的容器" "$nohealthcheck_containers"
}

check_5_27() {
  if [ -z "$containers" ]; then
    return
  fi

  local id="5.27"
  local desc="确保 docker 命令始终获取最新版本的镜像"
  local remediation="您应该使用适当的版本固定机制（默认分配的 <latest> 标记仍然容易受到缓存攻击）以避免提取缓存的旧版本。版本固定机制应该用于基础镜像、包和整个镜像。您可以根据需要自定义版本固定规则。"
  local remediationImpact="无"
  local check="$id - $desc"
  starttestjson "$id" "$desc"

  info -c "$check"
  logcheckresult "INFO"
}

check_5_28() {
  if [ -z "$containers" ]; then
    return
  fi

  local id="5.28"
  local desc="确保使用 PIDs cgroup限制被使用"
  local remediation="启动容器时使用具有适当值的 --pids-limit 标志。"
  local remediationImpact="无"
  local check="$id - $desc"
  starttestjson "$id" "$desc"

  fail=0
  nopids_limit_containers=""
  for c in $containers; do
    pidslimit="$(docker inspect --format '{{.HostConfig.PidsLimit }}' "$c")"

    if [ "$pidslimit" = "0" ] || [  "$pidslimit" = "<nil>" ] || [  "$pidslimit" = "-1" ]; then
      # 如果是第一个容器，则测试失败
      if [ $fail -eq 0 ]; then
        warn -s "$check"
        warn "      * 未设置 PID 限制: $c"
        nopids_limit_containers="$nopids_limit_containers $c"
        fail=1
        continue
      fi
      warn "      * 未设置 PID 限制: $c"
      nopids_limit_containers="$nopids_limit_containers $c"
    fi
  done
  # 我们遍历了所有容器，发现所有容器都有 PID 限制
  if [ $fail -eq 0 ]; then
    pass -s "$check"
    logcheckresult "PASS"
    return
  fi
  logcheckresult "WARN" "没有 PIDs cgroup 限制的容器" "$nopids_limit_containers"
}

check_5_29() {
  if [ -z "$containers" ]; then
    return
  fi

  local id="5.29"
  local desc="确保不使用 docker 的默认网桥 docker0"
  local remediation="您应该遵循 Docker 文档并设置用户定义的网络。所有容器都应该在这个网络中运行。"
  local remediationImpact="根据需要设置 PID 限制值。不正确的值可能会使容器无法使用。"
  local check="$id - $desc"
  starttestjson "$id" "$desc"

  fail=0
  docker_network_containers=""
  networks=$(docker network ls -q 2>/dev/null)
  for net in $networks; do
    if docker network inspect --format '{{ .Options }}' "$net" 2>/dev/null | grep "com.docker.network.bridge.name:docker0" >/dev/null 2>&1; then
      docker0Containers=$(docker network inspect --format='{{ range $k, $v := .Containers }} {{ $k }} {{ end }}' "$net" | \
        sed -e 's/^ //' -e 's/  /\n/g' 2>/dev/null)

      if [ -n "$docker0Containers" ]; then
        if [ $fail -eq 0 ]; then
          info -c "$check"
          fail=1
        fi
        for c in $docker0Containers; do
          if [ -z "$exclude" ]; then
            cName=$(docker inspect --format '{{.Name}}' "$c" 2>/dev/null | sed 's/\///g')
          else
            pattern=$(echo "$exclude" | sed 's/,/|/g')
            cName=$(docker inspect --format '{{.Name}}' "$c" 2>/dev/null | sed 's/\///g' | grep -Ev "$pattern" )
          fi
          if [ -n "$cName" ]; then
            info "      * Container in docker0 network: $cName"
            docker_network_containers="$docker_network_containers $c:$cName"
          fi
        done
      fi
    fi
  done
  # 我们遍历了所有容器，在 docker0 网络中没有找到
  if [ $fail -eq 0 ]; then
    pass -c "$check"
    logcheckresult "PASS"
    return
  fi
  logcheckresult "INFO" "Containers using docker0 network" "$docker_network_containers"
}

check_5_30() {
  if [ -z "$containers" ]; then
    return
  fi

  local id="5.30"
  local desc="确保不共享主机的用户命名空间"
  local remediation="您不应在主机和容器之间共享用户命名空间。"
  local remediationImpact="用户定义的网络需要根据组织安全策略进行配置和管理。"
  local check="$id - $desc"
  starttestjson "$id" "$desc"

  fail=0
  hostns_shared_containers=""
  for c in $containers; do
    if docker inspect --format '{{ .HostConfig.UsernsMode }}' "$c" 2>/dev/null | grep -i 'host' >/dev/null 2>&1; then
      # 如果是第一个容器，则测试失败
      if [ $fail -eq 0 ]; then
        warn -s "$check"
        warn "      * 共享的命名空间: $c"
        hostns_shared_containers="$hostns_shared_containers $c"
        fail=1
        continue
      fi
      warn "      * 共享的命名空间: $c"
      hostns_shared_containers="$hostns_shared_containers $c"
    fi
  done
  # 我们遍历了所有容器，没有发现与主机的用户命名空间共享的
  if [ $fail -eq 0 ]; then
    pass -s "$check"
    logcheckresult "PASS"
    return
  fi
  logcheckresult "WARN" "共享主机用户命名空间的容器" "$hostns_shared_containers"
}

check_5_31() {
  if [ -z "$containers" ]; then
    return
  fi

  local id="5.31"
  local desc="确保Docker套接字没有挂载在任何容器内"
  local remediation="您应该确保没有容器将 docker.sock 挂载为卷。"
  local remediationImpact="无"
  local check="$id - $desc"
  starttestjson "$id" "$desc"

  fail=0
  docker_sock_containers=""
  for c in $containers; do
    if docker inspect --format '{{ .Mounts }}' "$c" 2>/dev/null | grep 'docker.sock' >/dev/null 2>&1; then
      # 如果是第一个容器，则测试失败
      if [ $fail -eq 0 ]; then
        warn -s "$check"
        warn "      * Docker 套接字共享: $c"
        docker_sock_containers="$docker_sock_containers $c"
        fail=1
        continue
      fi
      warn "      * Docker 套接字共享: $c"
      docker_sock_containers="$docker_sock_containers $c"
    fi
  done
  # 我们浏览了所有容器，没有发现共享 docker.sock
  if [ $fail -eq 0 ]; then
    pass -s "$check"
    logcheckresult "PASS"
    return
  fi
  logcheckresult "WARN" "容器共享docker socket" "$docker_sock_containers"
}

check_5_end() {
  endsectionjson
}
